/*
 * tp68xx.c - TOPRO TP68xx USB webcam controller driver
 *
 * Copyright (C) 2009 Richard Case <tp68xx@racitup.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
/*
 * Description:
 *   Currently supports the following controllers: TP6801, TP6813.
 *   This is a straight USB to video4linux (V4L2) linux kernel module driver.
 *   User interface (V4L2) features include:
 *     - Basic read/write support
 *     - mmap Streaming I/O support
 *   Webcam interface features include:
 *
 * Manufacturer: http://www.topro.com.tw
 *   Datasheets for the controllers are available from this website.
 *
 * History:
 *   2009-Feb  Initial version - customised USB skeleton driver
 *
 */

/* ============================================================================
 * Includes
 */
#include <linux/usb.h>        /* Main USB API */
/*#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kref.h>
#include <asm/uaccess.h>
#include <linux/mutex.h>*/

/* ============================================================================
 * Local variable definitions (typedefs and structs)
 */

/* Structure to hold all of our device specific stuff */
struct tp68_usb_dev {
     struct usb_device    *udev;                  /* the usb device for this device */
     struct usb_interface *interface;             /* the interface for this device */
     struct semaphore     limit_sem;              /* limiting the number of writes in progress */
     struct usb_anchor    submitted;              /* in case we need to retract our submissions */
     unsigned char        *bulk_in_buffer;        /* the buffer to receive data */
     size_t               bulk_in_size;           /* the size of the receive buffer */
     __u8                 bulk_in_endpointAddr;   /* the address of the bulk in endpoint */
     __u8                 bulk_out_endpointAddr;  /* the address of the bulk out endpoint */
     int                  errors;                 /* the last request tanked */
     int                  open_count;             /* count the number of openers */
     spinlock_t           err_lock;               /* lock for errors */
     struct kref          kref;
     struct mutex         io_mutex;               /* synchronize I/O with disconnect */
};

/* ============================================================================
 * Defines and macros
 */
/*** TOPRO USB Vendor ID ***/
#define USB_TOPRO_VENDOR_ID        0x06a2

/*** Supported TOPRO Device IDs ***/
/* Matches fielded devices using TP6801 though datasheet lists 0x0001 */
#define USB_TP6801_PRODUCT_ID      0x0003
/* Matches the TP6813 datasheet: 0x0168 */
#define USB_TP6813_PRODUCT_ID      0x0168
/* May also work with TP6811: Product ID 0x6810 ? */

/*** TOPRO Interface Class ***/
#define USB_TOPRO_bInterfaceClass       0xff
#define USB_TOPRO_bInterfaceSubClass    0x00
#define USB_TOPRO_bInterfaceProtocol    0xff

/* Get a minor range for your devices from the usb maintainer */
#define USB_SKEL_MINOR_BASE     192

/* our private defines. if this grows any larger, use your own .h file */
#define MAX_TRANSFER          (PAGE_SIZE - 512)
/* MAX_TRANSFER is chosen so that the VM is not stressed by
   allocations > PAGE_SIZE and the number of packets in a page
   is an integer 512 is the largest possible packet on EHCI */
#define WRITES_IN_FLIGHT     8
/* arbitrarily chosen */

#define to_tp68_usb_dev(d) container_of(d, struct tp68_usb_dev, kref)

/* ============================================================================
 * Local shared variables, declared as 'static'
 */
/* Table of USB devices that work with this driver */
static struct usb_device_id tp68_usb_table [] = {
     { USB_DEVICE_AND_INTERFACE_INFO(   USB_TOPRO_VENDOR_ID,
                                        USB_TP6801_PRODUCT_ID,
                                        USB_TOPRO_bInterfaceClass,
                                        USB_TOPRO_bInterfaceSubClass,
                                        USB_TOPRO_bInterfaceProtocol ) },
     { USB_DEVICE_AND_INTERFACE_INFO(   USB_TOPRO_VENDOR_ID,
                                        USB_TP6813_PRODUCT_ID,
                                        USB_TOPRO_bInterfaceClass,
                                        USB_TOPRO_bInterfaceSubClass,
                                        USB_TOPRO_bInterfaceProtocol ) },
     { }                         /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, tp68_usb_table);

static struct usb_driver tp68_usb_driver;
static void tp68_usb_draw_down(struct tp68_usb_dev *dev);

/* ============================================================================
 * Global variables (should be avoided)
 */

/* ============================================================================
 * Private function prototypes, declared as 'static'
 */

/* ============================================================================
 * Public functions
 */

/* ============================================================================
 * Private functions, declared as 'static'
 */
static void tp68_usb_delete(struct kref *kref)
{
     struct tp68_usb_dev *dev = to_tp68_usb_dev(kref);

     usb_put_dev(dev->udev);
     kfree(dev->bulk_in_buffer);
     kfree(dev);
}

static int tp68_open(struct inode *inode, struct file *file)
{
     struct tp68_usb_dev *dev;
     struct usb_interface *interface;
     int subminor;
     int retval = 0;

     subminor = iminor(inode);

     interface = usb_find_interface(&tp68_usb_driver, subminor);
     if (!interface) {
          err ("%s - error, can't find device for minor %d",
               __func__, subminor);
          retval = -ENODEV;
          goto exit;
     }

     dev = usb_get_intfdata(interface);
     if (!dev) {
          retval = -ENODEV;
          goto exit;
     }

     /* increment our usage count for the device */
     kref_get(&dev->kref);

     /* lock the device to allow correctly handling errors
      * in resumption */
     mutex_lock(&dev->io_mutex);

     if (!dev->open_count++) {
          retval = usb_autopm_get_interface(interface);
               if (retval) {
                    dev->open_count--;
                    mutex_unlock(&dev->io_mutex);
                    kref_put(&dev->kref, tp68_usb_delete);
                    goto exit;
               }
     } /* else { //uncomment this block if you want exclusive open
          retval = -EBUSY;
          dev->open_count--;
          mutex_unlock(&dev->io_mutex);
          kref_put(&dev->kref, tp68_usb_delete);
          goto exit;
     } */
     /* prevent the device from being autosuspended */

     /* save our object in the file's private structure */
     file->private_data = dev;
     mutex_unlock(&dev->io_mutex);

exit:
     return retval;
}

static int tp68_release(struct inode *inode, struct file *file)
{
     struct tp68_usb_dev *dev;

     dev = (struct tp68_usb_dev *)file->private_data;
     if (dev == NULL)
          return -ENODEV;

     /* allow the device to be autosuspended */
     mutex_lock(&dev->io_mutex);
     if (!--dev->open_count && dev->interface)
          usb_autopm_put_interface(dev->interface);
     mutex_unlock(&dev->io_mutex);

     /* decrement the count on our device */
     kref_put(&dev->kref, tp68_usb_delete);
     return 0;
}

static int tp68_flush(struct file *file, fl_owner_t id)
{
     struct tp68_usb_dev *dev;
     int res;

     dev = (struct tp68_usb_dev *)file->private_data;
     if (dev == NULL)
          return -ENODEV;

     /* wait for io to stop */
     mutex_lock(&dev->io_mutex);
     tp68_usb_draw_down(dev);

     /* read out errors, leave subsequent opens a clean slate */
     spin_lock_irq(&dev->err_lock);
     res = dev->errors ? (dev->errors == -EPIPE ? -EPIPE : -EIO) : 0;
     dev->errors = 0;
     spin_unlock_irq(&dev->err_lock);

     mutex_unlock(&dev->io_mutex);

     return res;
}

static ssize_t tp68_read(struct file *file, char *buffer, size_t count, loff_t *ppos)
{
     struct tp68_usb_dev *dev;
     int retval;
     int bytes_read;

     dev = (struct tp68_usb_dev *)file->private_data;

     mutex_lock(&dev->io_mutex);
     if (!dev->interface) {          /* disconnect() was called */
          retval = -ENODEV;
          goto exit;
     }

     /* do a blocking bulk read to get data from the device */
     retval = usb_bulk_msg(dev->udev,
                     usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
                     dev->bulk_in_buffer,
                     min(dev->bulk_in_size, count),
                     &bytes_read, 10000);

     /* if the read was successful, copy the data to userspace */
     if (!retval) {
          if (copy_to_user(buffer, dev->bulk_in_buffer, bytes_read))
               retval = -EFAULT;
          else
               retval = bytes_read;
     }

exit:
     mutex_unlock(&dev->io_mutex);
     return retval;
}

static void tp68_usb_write_bulk_callback(struct urb *urb)
{
     struct tp68_usb_dev *dev;

     dev = urb->context;

     /* sync/async unlink faults aren't errors */
     if (urb->status) {
          if(!(urb->status == -ENOENT ||
              urb->status == -ECONNRESET ||
              urb->status == -ESHUTDOWN))
               err("%s - nonzero write bulk status received: %d",
                   __func__, urb->status);

          spin_lock(&dev->err_lock);
          dev->errors = urb->status;
          spin_unlock(&dev->err_lock);
     }

     /* free up our allocated buffer */
     usb_buffer_free(urb->dev, urb->transfer_buffer_length,
               urb->transfer_buffer, urb->transfer_dma);
     up(&dev->limit_sem);
}

static ssize_t tp68_write(struct file *file, const char *user_buffer, size_t count, loff_t *ppos)
{
     struct tp68_usb_dev *dev;
     int retval = 0;
     struct urb *urb = NULL;
     char *buf = NULL;
     size_t writesize = min(count, (size_t)MAX_TRANSFER);

     dev = (struct tp68_usb_dev *)file->private_data;

     /* verify that we actually have some data to write */
     if (count == 0)
          goto exit;

     /* limit the number of URBs in flight to stop a user from using up all RAM */
     if (down_interruptible(&dev->limit_sem)) {
          retval = -ERESTARTSYS;
          goto exit;
     }

     spin_lock_irq(&dev->err_lock);
     if ((retval = dev->errors) < 0) {
          /* any error is reported once */
          dev->errors = 0;
          /* to preserve notifications about reset */
          retval = (retval == -EPIPE) ? retval : -EIO;
     }
     spin_unlock_irq(&dev->err_lock);
     if (retval < 0)
          goto error;

     /* create a urb, and a buffer for it, and copy the data to the urb */
     urb = usb_alloc_urb(0, GFP_KERNEL);
     if (!urb) {
          retval = -ENOMEM;
          goto error;
     }

     buf = usb_buffer_alloc(dev->udev, writesize, GFP_KERNEL, &urb->transfer_dma);
     if (!buf) {
          retval = -ENOMEM;
          goto error;
     }

     if (copy_from_user(buf, user_buffer, writesize)) {
          retval = -EFAULT;
          goto error;
     }

     /* this lock makes sure we don't submit URBs to gone devices */
     mutex_lock(&dev->io_mutex);
     if (!dev->interface) {          /* disconnect() was called */
          mutex_unlock(&dev->io_mutex);
          retval = -ENODEV;
          goto error;
     }

     /* initialize the urb properly */
     usb_fill_bulk_urb(urb, dev->udev,
                 usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
                 buf, writesize, tp68_usb_write_bulk_callback, dev);
     urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
     usb_anchor_urb(urb, &dev->submitted);

     /* send the data out the bulk port */
     retval = usb_submit_urb(urb, GFP_KERNEL);
     mutex_unlock(&dev->io_mutex);
     if (retval) {
          err("%s - failed submitting write urb, error %d", __func__, retval);
          goto error_unanchor;
     }

     /* release our reference to this urb, the USB core will eventually free it entirely */
     usb_free_urb(urb);


     return writesize;

error_unanchor:
     usb_unanchor_urb(urb);
error:
     if (urb) {
          usb_buffer_free(dev->udev, writesize, buf, urb->transfer_dma);
          usb_free_urb(urb);
     }
     up(&dev->limit_sem);

exit:
     return retval;
}

static const struct file_operations tp68_fops = {
     .owner =     THIS_MODULE,
     .read =          tp68_read,
     .write =     tp68_write,
     .open =          tp68_open,
     .release =     tp68_release,
     .flush =     tp68_flush,
};

/*
 * usb class driver info in order to get a minor number from the usb core,
 * and to have the device registered with the driver core
 */
static struct usb_class_driver tp68_usb_class = {
     .name =          "tp68%d",
     .fops =          &tp68_fops,
     .minor_base =     USB_SKEL_MINOR_BASE,
};

static int tp68_usb_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
     struct tp68_usb_dev *dev;
     struct usb_host_interface *iface_desc;
     struct usb_endpoint_descriptor *endpoint;
     size_t buffer_size;
     int i;
     int retval = -ENOMEM;

     /* allocate memory for our device state and initialize it */
     dev = kzalloc(sizeof(*dev), GFP_KERNEL);
     if (!dev) {
          err("Out of memory");
          goto error;
     }
     kref_init(&dev->kref);
     sema_init(&dev->limit_sem, WRITES_IN_FLIGHT);
     mutex_init(&dev->io_mutex);
     spin_lock_init(&dev->err_lock);
     init_usb_anchor(&dev->submitted);

     dev->udev = usb_get_dev(interface_to_usbdev(interface));
     dev->interface = interface;

     /* set up the endpoint information */
     /* use only the first bulk-in and bulk-out endpoints */
     iface_desc = interface->cur_altsetting;
     for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
          endpoint = &iface_desc->endpoint[i].desc;

          if (!dev->bulk_in_endpointAddr &&
              usb_endpoint_is_bulk_in(endpoint)) {
               /* we found a bulk in endpoint */
               buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);
               dev->bulk_in_size = buffer_size;
               dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
               dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);
               if (!dev->bulk_in_buffer) {
                    err("Could not allocate bulk_in_buffer");
                    goto error;
               }
          }

          if (!dev->bulk_out_endpointAddr &&
              usb_endpoint_is_bulk_out(endpoint)) {
               /* we found a bulk out endpoint */
               dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
          }
     }
     if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {
          err("Could not find both bulk-in and bulk-out endpoints");
          goto error;
     }

     /* save our data pointer in this interface device */
     usb_set_intfdata(interface, dev);

     /* we can register the device now, as it is ready */
     retval = usb_register_dev(interface, &tp68_usb_class);
     if (retval) {
          /* something prevented us from registering this driver */
          err("Not able to get a minor for this device.");
          usb_set_intfdata(interface, NULL);
          goto error;
     }

     /* let the user know what node this device is now attached to */
     info("USB TOPRO tp68xx device now attached to USBSkel-%d", interface->minor);
     return 0;

error:
     if (dev)
          /* this frees allocated memory */
          kref_put(&dev->kref, tp68_usb_delete);
     return retval;
}

static void tp68_usb_disconnect(struct usb_interface *interface)
{
     struct tp68_usb_dev *dev;
     int minor = interface->minor;

     dev = usb_get_intfdata(interface);
     usb_set_intfdata(interface, NULL);

     /* give back our minor */
     usb_deregister_dev(interface, &tp68_usb_class);

     /* prevent more I/O from starting */
     mutex_lock(&dev->io_mutex);
     dev->interface = NULL;
     mutex_unlock(&dev->io_mutex);

     usb_kill_anchored_urbs(&dev->submitted);

     /* decrement our usage count */
     kref_put(&dev->kref, tp68_usb_delete);

     info("USB TOPRO tp68xx #%d now disconnected", minor);
}

static void tp68_usb_draw_down(struct tp68_usb_dev *dev)
{
     int time;

     time = usb_wait_anchor_empty_timeout(&dev->submitted, 1000);
     if (!time)
          usb_kill_anchored_urbs(&dev->submitted);
}

static int tp68_usb_suspend(struct usb_interface *intf, pm_message_t message)
{
     struct tp68_usb_dev *dev = usb_get_intfdata(intf);

     if (!dev)
          return 0;
     tp68_usb_draw_down(dev);
     return 0;
}

static int tp68_usb_resume (struct usb_interface *intf)
{
     return 0;
}

static int tp68_usb_pre_reset(struct usb_interface *intf)
{
     struct tp68_usb_dev *dev = usb_get_intfdata(intf);

     mutex_lock(&dev->io_mutex);
     tp68_usb_draw_down(dev);

     return 0;
}

static int tp68_usb_post_reset(struct usb_interface *intf)
{
     struct tp68_usb_dev *dev = usb_get_intfdata(intf);

     /* we are sure no URBs are active - no locking needed */
     dev->errors = -EPIPE;
     mutex_unlock(&dev->io_mutex);

     return 0;
}

static struct usb_driver tp68_usb_driver = {
     .name =          "tp68xx",
     .probe =     tp68_usb_probe,
     .disconnect =     tp68_usb_disconnect,
     .suspend =     tp68_usb_suspend,
     .resume =     tp68_usb_resume,
     .pre_reset =     tp68_usb_pre_reset,
     .post_reset =     tp68_usb_post_reset,
     .id_table =     tp68_usb_table,
     .supports_autosuspend = 1,
};

static int __init tp68_usb_init(void)
{
     int result;

     /* register this driver with the USB subsystem */
     result = usb_register(&tp68_usb_driver);
     if (result)
          err("usb_register failed. Error number %d", result);

     return result;
}

static void __exit tp68_usb_exit(void)
{
     /* deregister this driver with the USB subsystem */
     usb_deregister(&tp68_usb_driver);
}

module_init(tp68_usb_init);
module_exit(tp68_usb_exit);

MODULE_LICENSE("GPL");

/* ============================================================================
 * End of file
 */

